/*
 * DefaultMutableTreeNode.java  0.4.0 / Oct 25, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;

import com.cute4j.swing.Icon;
import com.cute4j.util.CommonConstants;


/**
 * Default Mutable Tree Node.
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class DefaultMutableTreeNode implements Cloneable, MutableTreeNode {

// Protected Fields. -----------------------------------------------------------

  protected MutableTreeNode parent;
  protected ArrayList children;
  protected Object userObject;
  protected boolean allowsChildren;

// Public Constructors. --------------------------------------------------------

  /**
   * Constructs new DefaultMutableTreeNode object.
   */
  public DefaultMutableTreeNode() {
    this(null, true);
  }

  /**
   * Constructs new DefaultMutableTreeNode object.
   *
   * @param userObject the user object.
   */
  public DefaultMutableTreeNode(Object userObject) {
    this(userObject, true);
  }

  /**
   * Constructs new DefaultMutableTreeNode object.
   *
   * @param userObject the user object.
   * @param allowsChildren if <tt>true</tt> the node allows children
   */
  public DefaultMutableTreeNode(Object userObject, boolean allowsChildren) {
    this.allowsChildren = allowsChildren;
    this.userObject = userObject;
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns the user object.
   *
   * @return the user object
   */
  public Object getUserObject() {
    return userObject;
  }

  /**
   * Adds a new child.
   *
   * @param child the child node
   *
   * @throws IllegalArgumentException if child is <tt>null</tt> or child is an
   *                                  ancestor
   * @throws IllegalStateException if the node does not allow children
   */
  public void add(MutableTreeNode child) {
    if ((child != null) && (child.getParent() == this)) {
      insert(child, getChildCount() - 1);
    } else {
      insert(child, getChildCount());
    }
  }

  /**
   * Returns <tt>true</tt> if the specified node is ancestor of the this node.
   *
   * @param node the specified node
   *
   * @return <tt>true</tt> if the specified node is ancestor of the this node
   */
  public boolean isNodeAncestor(TreeNode node) {
    if (node != null) {
      TreeNode current = this;
      while ((current != null) && (current != node)) { // NOPMD
        current = current.getParent();
      }
      if (current == node) { // NOPMD
        return true;
      }
    }
    return false;
  }

  /**
   * Returns <tt>true</tt> if the specified node is a child of this node.
   *
   * @param node the node
   *
   * @return <tt>true</tt> if the specified node is a child of this node
   */
  public boolean isNodeChild(TreeNode node) {
    if ((node == null) || (getChildCount() == 0)) {
      return false;
    }
    return (node.getParent() == this);
  }

  /**
   * Returns the first child of this node.
   *
   * @return the first child of this node
   *
   * @exception NoSuchElementException if the node has no children
   */
  public TreeNode getFirstChild() {
    if (getChildCount() == 0) {
      throw new NoSuchElementException("Node has no children"); // NOPMD
    }
    return getChildAt(0);
  }

  /**
   * Returns the last child of this node.
   *
   * @return the last child of this node
   *
   * @exception NoSuchElementException if the node has no children
   */
  public TreeNode getLastChild() {
    if (getChildCount() == 0) {
      throw new NoSuchElementException("Node has no children");
    }
    return getChildAt(getChildCount() - 1);
  }

  /**
   * Returns node's icon.
   * <p>
   *   <tt>NOTE: This method is preliminary.</tt>
   * </p>
   *
   * @return node's icon
   */
  public Icon getIcon() {
    return null;
  }

// MutableTreeNode Implementation. ---------------------------------------------

  /**
   * {@inheritDoc}
   */
  public TreeNode getParent() {
    return parent;
  }

  /**
   * {@inheritDoc}
   */
  public void setParent(MutableTreeNode parent) {
    this.parent = parent;
  }

  /**
   * {@inheritDoc}
   */
  public void removeFromParent() {
    if (parent != null) {
      parent.remove(this);
    }
  }

  /**
   * {@inheritDoc}
   */
  public boolean isLeaf() {
    if (children != null) {
      return children.isEmpty();
    }
    return true;
  }

  /**
   * {@inheritDoc}
   */
  public boolean getAllowsChildren() {
    return allowsChildren;
  }

  /**
   * {@inheritDoc}
   */
  public int getChildCount() {
    if (children != null) {
      return children.size();
    }
    return 0;
  }

  /**
   * {@inheritDoc}
   */
  public TreeNode getChildAt(int index) {
    if (getChildCount() == 0) {
      throw new ArrayIndexOutOfBoundsException("Node has no children");
    }
    return (TreeNode)children.get(index);
  }

  /**
   * {@inheritDoc}
   */
  public int getIndex(TreeNode node) {
    if (node == null) {
      throw new IllegalArgumentException("Null node");
    }
    if (node.getParent() != this) { // little opt.
      return -1;
    }
    return children.indexOf(node); // children != null
  }

  /**
   * {@inheritDoc}
   */
  public void insert(MutableTreeNode child, int index) {
    if (!allowsChildren) { // NOPMD
      throw new IllegalStateException("Children are not allowed");
    } else if (child == null) {
      throw new IllegalArgumentException("Null child");
    } else if (isNodeAncestor(child)) {
      throw new IllegalArgumentException("New child is an ancestor");
    }
    child.removeFromParent();
    child.setParent(this);
    if (children == null) {
      children = new ArrayList(3);
    }
    children.add(index, child);
  }

  /**
   * {@inheritDoc}
   */
  public void remove(int index) {
    if (getChildCount() == 0) {
      throw new ArrayIndexOutOfBoundsException("Node has no children");
    }
    ((MutableTreeNode)children.remove(index)).setParent(null);
    if (children.size() == 0) {
      children = null;
    }
  }

  /**
   * {@inheritDoc}
   */
  public void remove(MutableTreeNode node) {
    if (node == null) {
      throw new IllegalArgumentException("Null node");
    }
    if (node.getParent() != this) { // little opt.
      throw new IllegalArgumentException("Node is not a child");
    }
    if (children.remove(node)) { // children != null
      node.setParent(null);
    }
  }

  /**
   * {@inheritDoc}
   */
  public void setUserObject(Object object) {
    this.userObject = object;
  }

  /**
   * {@inheritDoc}
   */
  public Iterator children() {
    if (children == null) {
      return CommonConstants.EMPTY_ITERATOR;
    }
    return children.iterator();
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public Object clone() {
    try {
      DefaultMutableTreeNode clone = (DefaultMutableTreeNode)super.clone();
      clone.children = null;
      clone.parent = null;
      return clone;
    } catch (CloneNotSupportedException e) {
      throw new Error(e); // NOPMD
    }
  }

  /**
   * {@inheritDoc}
   */
  public String toString() {
    if (userObject != null) {
      return userObject.toString();
    }
    return CommonConstants.STRING_EMPTY;
  }

}
